%{
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h> /* provides access to the variable argument macros */
#include "calc.h"
#define SIZE 26

nodeType* opr(int oper, int nops, ...);
nodeType* con_or_id(int value_or_id, nodeEnum flag);
void freeNode(nodeType* p);
int dfs(nodeType* p);

void yyerror(char* s);

int symtab[SIZE];      /* symbol table */

extern int yylineno;

%}

/* value stack will be an array of these YYSTYPE's */ 
%union {
   int iValue;      /* integer value */
   char sIndex;      /* symbol table index */
   nodeType* nPtr;   /* node pointer */
};
/* generates the following:

   typedef union {
      int iValue;      
      char sIndex;
      nodeType* nPtr;
   } YYSTYPE;
   extern YYSTYPE yylval;
 */
/* in other words, constants, variables, and nodes can
   be represented by yylval in the parser's value stack */

/* binds INTEGER to iValue in the YYSTYPE union */
/* associates token names with correct component of the YYSTYPE union */
/* to generate following code */
/* yylval.nPtr = con(yyvsp[0].iValue); */

%token <iValue> INTEGER
%token <sIndex> VARIABLE
%token WHILE IF PRINT
/* binds expr to nPtr in the YYSTYPE union */
%type <nPtr> stmt expr stmt_list

%nonassoc IFX
%nonassoc ELSE 
%left GE LE EQ NE '>' '<'
%left '+' '-'
%left '*' '/'
%right '^'
%nonassoc UMINUS

%%

program: function                     { exit(0); }
         ;

function: function stmt               { dfs($2); freeNode($2); }
          | /* NULL */

stmt: ';'                              { $$ = opr(';', 2, NULL, NULL); }
      | expr ';'                       { $$ = $1; }
      | PRINT expr ';'                 { $$ = opr(PRINT, 1, $2); }
      | VARIABLE '=' expr ';'          { $$ = opr('=', 2, con_or_id($1,typeId), $3); }
      | WHILE '(' expr ')' stmt        { $$ = opr(WHILE, 2, $3, $5); }
      | IF '(' expr ')' stmt %prec IFX  { $$ = opr(IF, 2, $3, $5); }
      | IF '(' expr ')' stmt ELSE stmt { $$ = opr(IF, 3, $3, $5, $7); }
      | '{' stmt_list '}'              { $$ = $2; }
      ;

stmt_list: stmt                        { $$ = $1; }
           | stmt_list stmt            { $$ = opr(';', 2, $1, $2); }
           ;

expr: INTEGER                          { $$ = con_or_id($1, typeCon); }
      | VARIABLE                       { $$ = con_or_id($1, typeId); }
      | '-' expr %prec UMINUS          { $$ = opr(UMINUS, 1, $2); }
      | expr '+' expr                  { $$ = opr('+', 2, $1, $3); }
      | expr '-' expr                  { $$ = opr('-', 2, $1, $3); }
      | expr '*' expr                  { $$ = opr('*', 2, $1, $3); }
      | expr '/' expr                  { $$ = opr('/', 2, $1, $3); }
      | expr '^' expr                  { $$ = opr('^', 2, $1, $3); }
      | expr '<' expr                  { $$ = opr('<', 2, $1, $3); }
      | expr '>' expr                  { $$ = opr('>', 2, $1, $3); }
      | expr GE expr                   { $$ = opr(GE, 2, $1, $3); }
      | expr LE expr                   { $$ = opr(LE, 2, $1, $3); }
      | expr NE expr                   { $$ = opr (NE, 2, $1, $3); }
      | expr EQ expr                   { $$ = opr (EQ, 2, $1, $3); }
      | '(' expr ')'                   { $$ = $2; }
      ;

%%

/* returns a pointer to node for a constant */
/* parse tree constructed bottom-up */
nodeType* con_or_id(int value_or_id, nodeEnum flag) {

   nodeType* p;

   /* allocate node */
   if ((p = malloc (sizeof (nodeType))) == NULL)
      yyerror ("out of memory");

   /* copy information */
   p->type = flag;
   p->con_or_id.value_or_i = value_or_id;

   return p;
}

/* returns a pointer to a node for an operator */
/* when an operator is encountered, a node is allocated
   and pointers to previously allocated nodes are entered as operands */
nodeType* opr (int oper, int nops, ...) {

   /* argument pointer */
   /* for variable number of operands */
   va_list ap; 

   nodeType* p;

   int i;

   /* allocate node */
   size_t nodeSize = sizeof (nodeType) + ((nops-1) * sizeof (nodeType*));

   if ((p = malloc (nodeSize)) == NULL)
      yyerror ("out of memory");

   /* copy information */
   p->type = typeOpr;
   p->opr.oper = oper;
   p->opr.nops = nops;

   va_start(ap, nops);

   for (i = 0; i < nops; i++)
      p->opr.op[i] = va_arg (ap, nodeType*);
   va_end (ap);

   return p;
}

void freeNode(nodeType* p) {

   int i;

   if (!p)
      return;

   if (p->type == typeOpr)

      for (i = 0; i < p->opr.nops; i++)
         freeNode(p->opr.op[i]);
  
   free(p); 
}

void yyerror(char* s) {
   fprintf(stderr, "line %d: %s\n", yylineno, s);
}

int main(void) {
   yyparse();
   return 0;
}
